package cn.xinfei.xdecision.common.service.component.scriptrule.impl;

import cn.hutool.core.date.DateUtil;
import cn.xinfei.xdecision.common.dao.component.scriptrule.RuleScriptVersionMapper;
import cn.xinfei.xdecision.common.model.common.PageResult;
import cn.xinfei.xdecision.common.model.common.ResponseEntityDto;
import cn.xinfei.xdecision.common.model.common.requestParam.AssemblyRuleCodeReq;
import cn.xinfei.xdecision.common.model.component.ComponentAction;
import cn.xinfei.xdecision.common.model.component.guiderule.RuleInfo;
import cn.xinfei.xdecision.common.model.component.guiderule.vo.RuleVersionRespVo;
import cn.xinfei.xdecision.common.model.component.scriptrule.RuleMixExpressionCheck;
import cn.xinfei.xdecision.common.model.component.scriptrule.RuleMixExpressionCheckResult;
import cn.xinfei.xdecision.common.model.component.scriptrule.RuleScriptFieldRel;
import cn.xinfei.xdecision.common.model.component.scriptrule.RuleScriptVersion;
import cn.xinfei.xdecision.common.model.datax.datamanage.Field;
import cn.xinfei.xdecision.common.model.login.SessionManager;
import cn.xinfei.xdecision.common.service.common.sso.SSOService;
import cn.xinfei.xdecision.common.service.component.ComponentCommonContext;
import cn.xinfei.xdecision.common.service.component.componentaction.ComponentActionService;
import cn.xinfei.xdecision.common.service.component.guiderule.RuleService;
import cn.xinfei.xdecision.common.service.component.scriptrule.RuleScriptVersionService;
import cn.xinfei.xdecision.common.service.script.ExpressionUtil;
import cn.xinfei.xdecision.common.utils.constant.SysConstant;
import cn.xinfei.xdecision.common.utils.enums.enginex.ComponentStatusEnum;
import cn.xinfei.xdecision.common.utils.enums.enginex.ComponentTypeEnum;
import cn.xinfei.xdecision.common.utils.enums.enginex.ComponentVersionStatusEnum;
import cn.xinfei.xdecision.common.utils.exception.ApiException;
import cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum;
import cn.xinfei.xdecision.common.utils.util.BeanUtilsPlus;
import cn.xinfei.xdecision.common.utils.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum.*;
import static cn.xinfei.xdecision.common.utils.exception.util.ApiExceptionUtil.exception;

/**
 * (RuleScriptVersion)表服务实现类
 */
@Service("ruleScriptVersionService")
@Slf4j
public class RuleScriptVersionServiceImpl extends ServiceImpl<RuleScriptVersionMapper, RuleScriptVersion> implements RuleScriptVersionService {
    @Resource
    private RuleScriptVersionMapper ruleScriptVersionMapper;
    @Resource
    private RuleScriptFieldRelServiceImpl ruleScriptFieldRelService;
    @Resource
    private RuleService ruleService;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private SSOService ssoService;

    @Resource
    private ComponentActionService componentActionService;

    @Resource(name = "expression")
    private ExpressionUtil expressionUtil;


    @Override
    public RuleScriptVersion queryById(Long id) {
        if (id != null) {
            RuleScriptVersion ruleScriptVersion = this.getById(id);
            if (ruleScriptVersion == null) {
                return null;
            }
            ruleScriptVersion.setStatusDesc(Objects.requireNonNull(ComponentVersionStatusEnum.get(ruleScriptVersion.getStatus())).getName());
            ruleScriptVersion.setVersionNoDesc("V" + ruleScriptVersion.getVersionNo());
            ruleScriptVersion.setComponentActionList(
                    componentActionService.getComponentActionByComponentCode(
                            ruleScriptVersion.getRuleCode(),
                            ruleScriptVersion.getVersionNo(),
                            Collections.singletonList(ComponentTypeEnum.SCRIPT_RULE_BASE.getType())));
            return ruleScriptVersion;
        }
        return null;
    }

    @Override
    public RuleScriptVersion getVersionByVersionNo(String ruleCode, Long versionNo) {
        LambdaQueryWrapper<RuleScriptVersion> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RuleScriptVersion::getRuleCode, ruleCode);
        wrapper.eq(RuleScriptVersion::getVersionNo, versionNo);
//        wrapper.eq(RuleScriptVersion::getStatus,1);
        RuleScriptVersion list = this.getOne(wrapper);
        return list;
    }

    public List<RuleScriptVersion> getVersionByStatus(String ruleCode, int status) {
        QueryWrapper<RuleScriptVersion> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("rule_code", ruleCode);
        queryWrapper.eq("status", status);

        return this.list(queryWrapper);
    }

    @Override
    public List<RuleScriptVersion> queryVersionListByRuleId(Long ruleId) {
        LambdaQueryWrapper<RuleScriptVersion> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RuleScriptVersion::getRuleId, ruleId);
//        wrapper.eq(RuleScriptVersion::getStatus,1);
        wrapper.orderByDesc(RuleScriptVersion::getId);
        List<RuleScriptVersion> list = this.list(wrapper);
        list.forEach(x -> x.setComponentActionList(
                componentActionService.getComponentActionByComponentCode(
                        x.getRuleCode(),
                        x.getVersionNo(),
                        Collections.singletonList(ComponentTypeEnum.SCRIPT_RULE_BASE.getType()))));
        return list;
    }

    @Override
    public List<RuleScriptVersion> queryVersionByRuleCodes(List<String> ruleCodes, Integer status) {
        if (CollectionUtils.isEmpty(ruleCodes)) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<RuleScriptVersion> lbd = new LambdaQueryWrapper<RuleScriptVersion>()
                .in(RuleScriptVersion::getRuleCode, ruleCodes);
        if (Objects.nonNull(status)) {
            lbd.eq(RuleScriptVersion::getStatus, status);
        }
        return this.list(lbd);
    }

    @Override
    public List<String> queryFieldEnByVersionId(Long versionId) {
        Set<String> fieldEnSet = new HashSet<>();
        RuleScriptVersion ruleScriptVersion = this.queryById(versionId);
        if (ruleScriptVersion == null) {
            return new ArrayList<>();
        }
        String scriptContent = ruleScriptVersion.getScriptContent();
        if (StringUtils.isNotBlank(scriptContent)) {
            JSONObject jsonObject = JSON.parseObject(scriptContent);
            Object farr = jsonObject.get("farr");
            if (farr != null && !"".equals(farr)) {
                List<Field> fieldList = JSONArray.parseArray(JSON.toJSONString(farr), Field.class);
                fieldEnSet.addAll(fieldList.stream().map(item -> {
                    return item.getFieldCode();
                }).collect(Collectors.toSet()));
            }
        }
        return new ArrayList<>(fieldEnSet);
    }

    @Override
    @Transactional
    public List<Long> addVersionList(List<RuleScriptVersion> versionList) {
        List<Long> newVersionIds = new ArrayList<>();
        for (RuleScriptVersion ruleScriptVersion : versionList) {
            newVersionIds.add(this.addVersion(ruleScriptVersion));
        }
        return newVersionIds;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long checkAndAddVersion(RuleScriptVersion version) {
        if (StringUtils.isEmpty(version.getRuleCode())) {
            throw new ApiException(PARAMS_EXCEPTION);
        }
        RuleInfo ruleInfo = ruleService.queryByCode(version.getRuleCode());
        if (ruleInfo == null) {
            throw exception(COMPONENT_NOT_EXISTS, version.getRuleCode());
        }
        //更新规则的更新人，更新时间
        ruleInfo.setUpdatedTime(DateUtil.now());
        ruleInfo.setUpdateUserId(SessionManager.getLoginUserId());
        if (!ruleService.updateById(ruleInfo)) {
            throw new ApiException(ErrorCodeEnum.DECISION_VERSION_NEW_ERROR);
        }

        //填充规则属性值
        version.setRuleId(ruleInfo.getId());
        //忽略规则的description
        //BeanUtils.copyProperties(ruleInfo, version, "description");
        //查询规则版本
        QueryWrapper<RuleScriptVersion> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("rule_code", version.getRuleCode());
        List<RuleScriptVersion> scriptVersions = this.list(queryWrapper);

        //新建初始版本
        if (CollectionUtils.isEmpty(scriptVersions)) {
            this.save(version);
            saveVersionFiledRel(version);
            //保存动作配置
            componentActionService.saveOrUpdateComponentAction(
                    version.getComponentActionList(),
                    version.getRuleCode(),
                    ComponentTypeEnum.SCRIPT_RULE_BASE.getType(),
                    SysConstant.ONE);
            return version.getId();
        }

        //新增版本,有【新建】状态版本覆盖，没有则新增
        List<RuleScriptVersion> newVersion = getVersionByStatus(version.getRuleCode(), ComponentVersionStatusEnum.NEW.getStatus());

        Long versionNo;
        if (!CollectionUtils.isEmpty(newVersion)) {
            versionNo = newVersion.get(0).getVersionNo();
            version.setId(newVersion.get(0).getId());
            version.setStatus(ComponentStatusEnum.NEW.getStatus());
            this.updateById(version);
            saveVersionFiledRel(version);
        } else {
            Long newVersionNo = ruleScriptVersionMapper.getMaxVersionNo(version.getRuleId()) + 1;
            versionNo = newVersionNo;
            version.setVersionNo(newVersionNo);
            version.setStatus(ComponentStatusEnum.NEW.getStatus());
            this.save(version);
            saveVersionFiledRel(version);
        }
        //保存动作配置
        componentActionService.saveOrUpdateComponentAction(
                version.getComponentActionList(),
                version.getRuleCode(),
                ComponentTypeEnum.SCRIPT_RULE_BASE.getType(),
                versionNo);
        return version.getId();
    }

    @Override
    public Long addVersion(RuleScriptVersion version) {
        if (StringUtils.isEmpty(version.getRuleCode())) {
            throw new ApiException(PARAMS_EXCEPTION.getCode(), PARAMS_EXCEPTION.getMessage());
        }
        RuleInfo ruleInfo = ruleService.queryByCode(version.getRuleCode());
        if (ruleInfo == null) {
            throw exception(COMPONENT_NOT_EXISTS, version.getRuleCode());
        }
        //更新规则的更新人，更新时间
        ruleInfo.setUpdatedTime(DateUtil.now());
        ruleInfo.setUpdateUserId(SessionManager.getLoginUserId());
        if (!ruleService.updateById(ruleInfo)) {
            throw new ApiException(ErrorCodeEnum.DECISION_VERSION_NEW_ERROR);
        }

        //填充规则属性值
        version.setRuleId(ruleInfo.getId());
        //忽略规则的description
//        BeanUtils.copyProperties(ruleInfo, version, "description");
        if (version.getStatus() == null) {
            version.setStatus(ComponentVersionStatusEnum.NEW.getStatus());
        }
        if (version.getVersionNo() == null) {
            Long maxVersionNo = ruleScriptVersionMapper.getMaxVersionNo(ruleInfo.getId());
            version.setVersionNo(maxVersionNo + 1);
        }
        this.save(version);
        saveVersionFiledRel(version);
        componentActionService.saveOrUpdateComponentAction(
                version.getComponentActionList(),
                version.getRuleCode(),
                ComponentTypeEnum.SCRIPT_RULE_BASE.getType(),
                version.getVersionNo());
        return version.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateVersion(RuleScriptVersion version) {
        Long versionId = version.getId();
        if (versionId == null) {
            return false;
        }
        //修改版本主表
        int updateById = ruleScriptVersionMapper.updateById(version);

        saveVersionFiledRel(version);
        List<ComponentAction> componentActionList = version.getComponentActionList();
        //保存动作配置
        componentActionService.saveOrUpdateComponentAction(version.getComponentActionList(),
                version.getRuleCode(),
                ComponentTypeEnum.SCRIPT_RULE_BASE.getType(),
                version.getVersionNo());
        return updateById > 0;
    }

    private void saveVersionFiledRel(RuleScriptVersion version) {
        QueryWrapper<RuleScriptFieldRel> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("version_id", version.getId());
        queryWrapper.eq("rule_id", version.getRuleId());

        List<RuleScriptFieldRel> rels = ruleScriptFieldRelService.list(queryWrapper);
        if (!CollectionUtils.isEmpty(rels)) {
            List<Long> ids = rels.stream().map(RuleScriptFieldRel::getId).collect(Collectors.toList());
            ruleScriptFieldRelService.removeByIds(ids);
        }

        String fieldCodesStr = version.getFieldCodes();
        List<RuleScriptFieldRel> newRelList = new ArrayList<>();
        if (StringUtil.isValidStr(fieldCodesStr)) {
            List<String> fieldCodes = Arrays.asList(fieldCodesStr.split(","));

            List<String> distinctFieldCodes = fieldCodes.stream().distinct().collect(Collectors.toList());

            distinctFieldCodes.forEach(fieldCode -> {
                RuleScriptFieldRel rel = new RuleScriptFieldRel();
                rel.setRuleId(version.getRuleId());
                rel.setRuleCode(version.getRuleCode());
                rel.setVersionId(version.getId());
                rel.setFieldCode(fieldCode);

                newRelList.add(rel);

            });
        }

        if (!newRelList.isEmpty()) {
            ruleScriptFieldRelService.saveBatch(newRelList);
        }
    }

    @Override
    public void lock(Long versionId) {
        RuleScriptVersion ruleVersion = getById(versionId);
        if (!Objects.equals(ruleVersion.getStatus(), ComponentVersionStatusEnum.NEW.getStatus())) {
            throw exception(COMPONENT_VERSION_LOCK_ERROR_1, ComponentVersionStatusEnum.getNameByStatus(ruleVersion.getStatus()));
        }

        RuleInfo ruleInfo = ruleService.queryByCode(ruleVersion.getRuleCode());
        if (ruleInfo == null) {
            throw exception(COMPONENT_NOT_EXISTS, ruleVersion.getRuleCode());
        }

        //校验当前版本信息完整性
        Set<String> emptyFieldSet = new HashSet<>();
        ComponentCommonContext.checkParamNotEmpty(ruleVersion, emptyFieldSet);
        if (!CollectionUtils.isEmpty(emptyFieldSet)) {
            log.info(ErrorCodeEnum.INCOMPLETE_COMPONENT_INFORMATION.getMessage() + ",versionId:{},emptyFieldSet:{}",
                    versionId, JSONObject.toJSONString(emptyFieldSet));
            throw new ApiException(ErrorCodeEnum.INCOMPLETE_COMPONENT_INFORMATION);
        }

        ruleVersion.setStatus(ComponentVersionStatusEnum.ENABLE.getStatus());
        transactionTemplate.execute(status -> {
            try {
                this.updateById(ruleVersion);
                ruleInfo.setStatus(ComponentStatusEnum.ENABLE.getStatus());
                ruleService.updateById(ruleInfo);
                return Boolean.TRUE;
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("锁定版本失败，版本id==>{}", versionId, e);
                throw exception(ErrorCodeEnum.COMPONENT_VERSION_LOCK_ERROR_2, ruleVersion.getVersionNo());
            }
        });
    }


    @Override
    public List<String> queryFieldEnByVersionIds(List<Long> versionIds) {
        LambdaQueryWrapper<RuleScriptVersion> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(RuleScriptVersion::getId, versionIds);
        List<RuleScriptVersion> list = this.list(wrapper);
        Set<String> fieldEnSet = new HashSet<>();
        if (list != null && !list.isEmpty()) {
            for (RuleScriptVersion ruleScriptVersion : list) {
                collectFieldEn(ruleScriptVersion, fieldEnSet);
            }
        }
        return new ArrayList<>(fieldEnSet);
    }

    @Override
    public ResponseEntityDto<PageResult<List<RuleVersionRespVo>, RuleScriptVersion>> listByRuleCode(AssemblyRuleCodeReq req) {
        PageMethod.startPage(req.getPageNo(), req.getPageSize());

        List<RuleScriptVersion> list = this.list(new LambdaQueryWrapper<RuleScriptVersion>()
                .eq(RuleScriptVersion::getRuleCode, req.getRuleCode())
                .orderByDesc(RuleScriptVersion::getUpdatedTime));

        List<RuleVersionRespVo> ruleVersionRespVos =
                BeanUtilsPlus.copyElements(list, RuleVersionRespVo::new);
        if (CollectionUtils.isEmpty(list)) {
            return PageResult.success(new ArrayList<>(), new PageInfo<>());
        }
        ssoService.fillUserInfoToVO(ruleVersionRespVos);

        ruleVersionRespVos.forEach(x -> {
            x.setVersionNoDesc("V" + x.getVersionNo());
            x.setStatusDesc(Objects.requireNonNull(ComponentVersionStatusEnum.get(x.getStatus())).getName());
        });
        return PageResult.success(ruleVersionRespVos, new PageInfo<>(list));
    }

    private void collectFieldEn(RuleScriptVersion ruleScriptVersion, Set<String> fieldEnSet) {
        String scriptContent = ruleScriptVersion.getScriptContent();
        if (StringUtils.isNotBlank(scriptContent)) {
            JSONObject jsonObject = JSON.parseObject(scriptContent);
            Object farr = jsonObject.get("farr");
            if (farr != null && !"".equals(farr)) {
                List<Field> fieldList = JSONArray.parseArray(JSON.toJSONString(farr), Field.class);
                fieldEnSet.addAll(fieldList.stream().map(item -> {
                    return item.getFieldCode();
                }).collect(Collectors.toSet()));
            }
        }
    }

    @Override
    public List<RuleMixExpressionCheckResult> scriptRulecheck(RuleMixExpressionCheck mixExpressionCheck) {
        return expressionUtil.testScriptRule(mixExpressionCheck);
    }

}
